/**************************************************************************//**
 * @file     sys_init.c
 * @brief    系统初始化
 * @version  V0.0.1
 * @date     2020-02-01
 ******************************************************************************/
/*
 * Copyright (c) 2004, 2005 by xiao xiang. All rights reserved.
 *
 * Permission to use, copy, modify, and distribute this software
 * is freely granted, provided that this notice is preserved.
 */
#include "stm32f1xx.h"

#include "common.h"
#include "config_api.h"
#include "sys_api.h"
#include "log_api.h"
#include "hal_stm32f1xx_flash_api.h"
#include "hal_stm32f1xx_rcc_api.h"
#include "hal_stm32f1xx_core_api.h"
#include "hal_stm32f1xx_gpio_api.h"
#include "hal_stm32f1xx_uart_api.h"

#include "sys_init.h"
#include "sys_core.h"

// CPU Core关键寄存器初始化
uint32_t SysCoreInit(void)
{
    // FLASH OBR数据
    LOG_INFO("Flash OBR = %x\r\n", FLASH->OBR.reg);

    // 获取芯片ID/版本等相关信息

    // 使能Flash预取功能，提升性能
    HAL_FLASH_PREFETCH_BUFFER_ENABLE();

    // 设置抢占和响应中断优先级相关配置
    NVIC_SetPriorityGrouping((uint32_t)NVIC_PRIORITYGROUP_4);

    return RET_OK;
}

// 初始化系统时钟，切换到外部时钟，系统频率调整为72MHz
uint32_t SysClockInit(void)
{
    uint32_t ret;

    // 配置晶振和时钟，切换系统频率至72M
    ret = HAL_RccSwitchHse8mClk72m();
    if (ret != RET_OK) {
        return RET_ERROR;
    }

    return RET_OK;
}

// USART1模块初始化
uint32_t SysUsart1Init(void)
{
    USART_TypeDef *pUsart = USART1;
    UN_GPIO_CRH unCrh;

    // Step1: 将串口对应的GPIO设置成复用功能
    unCrh.MODE9 = GPIO_CRC_CNF_OUPUT_AF_PP;     // GPIO9 = UART TX,设置为推挽式输出
    unCrh.CNF9 = GPIO_CRX_MODE_OUTPUT_50M;      // 输出为高速模式
    unCrh.MODE10 = GPIO_CRX_MODE_INPUT;         // GPIO10 = UART RX,设置为输入
    unCrh.CNF10 = GPIO_CRC_CNF_INPUT_FLOATING;  // 输入为无上拉和下拉模式

    GPIOA->CRH.reg = unCrh.reg;

    // Step2: 配置前先关闭串口
    pUsart->CR1.UE = DISABLE;   // 关闭串口
    
    // Step3: 编程M定义字长
    pUsart->CR1.M = 0;          // 一个起始位， 8个数据位， n个停止位
    
    // Step4: 编程停止位的位数和校验位
    pUsart->CR1.PCE  = DISABLE; // 禁止奇偶校验功能
    pUsart->CR2.STOP = 0;       // 一个停止位
    
    // Step5: 设置波特率为115200，默认PCLK频率为8MB
    // 波特率 = PCLK频率 / (16 * DIV)
    pUsart->BRR.DIV_Mantissa = 39;   // 整数部分,115200=39
    pUsart->BRR.DIV_Fraction = 1;   // 小数部分,115200=1

    // Step6: 使能接收发送功能
    pUsart->CR1.RE = ENABLE;    // 使能串口接收功能
    pUsart->CR1.TE = ENABLE;    // 使能串口发送功能，该操作会发送一个空闲帧，并清除TXE位

    // Step7: 配置中断
    pUsart->CR1.IDLEIE = DISABLE;   // 禁止IDLE中断
    pUsart->CR1.RXNEIE = DISABLE;   // 禁止RXNEIE中断，接收缓冲区非空
    pUsart->CR1.TCIE   = DISABLE;   // 禁止TCIE发送完成中断
    pUsart->CR1.TXEIE  = DISABLE;   // 禁止TXEIE发送缓冲区为空中断
    pUsart->CR1.PEIE   = DISABLE;   // 禁止PEIE中断
    
    // Step8: 配置DMA

    // Step9: 关闭其它功能不需要的功能，比如LIN模式/红外等

    // Step9: 设置UE激活USART和发送接收功能
    pUsart->CR1.UE = ENABLE;        // 使能串口

    return RET_OK;
}

// 初始化系统TICK时钟，固定为1ms周期
uint32_t SysTickInit(void)
{
    uint32_t prioritygroup;
    uint32_t curFreq = HAL_RccSysclkFreqGet();

    // 配置SYSTICK触发频率为1ms，假设频率为8MHz，则计数值设置=8K，即每8K cycle即1ms产生一次TICK中断
    // 里面使能了SysTick，由于Systick中断默认打开，所以无需手动打开
    SysTick_Config(curFreq / 1000);

    // 配置SYSTICK中断优先级，也可以不用配置，前面使能的时候已经默认配置好
    prioritygroup = NVIC_GetPriorityGrouping();
    NVIC_SetPriority(SysTick_IRQn, NVIC_EncodePriority(prioritygroup, 0, 0));

    return RET_OK;
}

// 提前使能必要的硬件模块时钟
uint32_t SysHwClkInit(void)
{
    // Power电源模块时钟打开
    RCC->APB1ENR.PWREN = ENABLE;

    // Backup备份模块时钟打开
    RCC->APB1ENR.BKPEN = ENABLE;

    // 使能GPIOA时钟，用于PA0按键、PA9/PA10 USART串口输出
    RCC->APB2ENR.IOPAEN = ENABLE;

    // 使能GPIOB时钟，用于I2C、SPI、ADC、USART3功能
    RCC->APB2ENR.IOPBEN = ENABLE;

    // 使能GPIOC时钟，用于PC13引脚LED、USART2、SPI
    RCC->APB2ENR.IOPCEN = ENABLE;

    // AFIO模块时钟打开，完成之后才能配置SWJ调试功能
    RCC->APB2ENR.AFIOEN = ENABLE;

    return RET_OK;
}

// 使能或禁止调试功能
uint32_t SysSwjInit(void)
{
    // 按需求打开调试功能，DEBUG版本才需要打开
#ifdef DEBUG
    HAL_AFIO_REMAP_SWJ_CONFIG(HAL_AFIO_REMAP_SWJ_ENABLE);
#endif

    return RET_OK;
}

// GPIO模块初始化
uint32_t SysGpioInit(void)
{
    UN_GPIO_CRH unGpioCr;

    // 配置PC13引脚为开漏输出模式，用于点灯
    unGpioCr.reg = GPIOC->CRH.reg;
    unGpioCr.MODE13 = GPIO_CRX_MODE_OUTPUT_2M;
    unGpioCr.CNF13  = GPIO_CRC_CNF_OUPUT_OD;
    GPIOC->CRH.reg  = unGpioCr.reg;

    return RET_OK;
}

// DMA模块初始化
uint32_t SysDmaInit(void)
{
    return RET_OK;
}

// 初始化函数数组，按顺序逐个执行
const SYS_INIT_PF g_apfSysInit[] = {
    SYS_ResetTypeInit,
    SysCoreInit,
    SysClockInit,
    SysUsart1Init,
    SysTickInit,
    SysHwClkInit,
    SysSwjInit,
    SysGpioInit,
    SysDmaInit
};

// 系统跳转到Main函数后的初始化总入口
void SYS_Init(void)
{
    uint32_t ret;
    uint32_t i;
    SYS_INIT_PF pfFunc = NULL;

    for (i = 0; i < ARR_SIZE(g_apfSysInit); i++) {
        LOG_INFO("SYS init step %u\r\n", i);
        pfFunc = g_apfSysInit[i];
        ret = pfFunc();
        if (ret != RET_OK) {
            LOG_WARNING("SYS_Init fail\r\n");
        }
    }

    return;
}

// 使能必须的硬件模块时钟
uint32_t SysHwClkInitBeforeMain(void)
{
    // 使能GPIOC时钟，用于PC13引脚LED
    RCC->APB2ENR.IOPCEN = ENABLE;

    // 使能GPIOA时钟，用于PA0按键、PA9/PA10 USART串口输出
    RCC->APB2ENR.IOPAEN = ENABLE;

    // 使能USART1串口时钟
    RCC->APB2ENR.USART1EN = ENABLE;

    return RET_OK;
}

// GPIO/AFIO模块早期初始化
uint32_t SysGpioInitBeforeMain(void)
{
    UN_GPIO_CRH unGpioCr;

    // 配置PC13引脚为开漏输出模式，用于点灯
    unGpioCr.reg = GPIOC->CRH.reg;
    unGpioCr.MODE13 = GPIO_CRX_MODE_OUTPUT_2M;
    unGpioCr.CNF13  = GPIO_CRC_CNF_OUPUT_PP;
    GPIOC->CRH.reg  = unGpioCr.reg;

    // 设置PC13引脚为高电平或低电平
    // GPIOC->BSRR.BSy16 = BIT(13);    // 设置为高电平
    GPIOC->BRR.BRy16  = BIT(13);       // 设置为低电平

    return RET_OK;
}

// 串口模块早期初始化，只初始化固定的USART，早期频率还是8MHz
uint32_t SysUsart1InitBeforeMain(void)
{
    USART_TypeDef *pUsart = USART1;
    UN_GPIO_CRH unCrh;

    // Step1: 将串口对应的GPIO设置成复用功能
    unCrh.MODE9 = GPIO_CRC_CNF_OUPUT_AF_PP;     // GPIO9 = UART TX,设置为推挽式输出
    unCrh.CNF9 = GPIO_CRX_MODE_OUTPUT_50M;      // 输出为高速模式
    unCrh.MODE10 = GPIO_CRX_MODE_INPUT;         // GPIO10 = UART RX,设置为输入
    unCrh.CNF10 = GPIO_CRC_CNF_INPUT_FLOATING;  // 输入为无上拉和下拉模式

    GPIOA->CRH.reg = unCrh.reg;

    // Step2: 配置前先关闭串口
    pUsart->CR1.UE = DISABLE;   // 关闭串口
    
    // Step3: 编程M定义字长
    pUsart->CR1.M = 0;          // 一个起始位， 8个数据位， n个停止位
    
    // Step4: 编程停止位的位数和校验位
    pUsart->CR1.PCE  = DISABLE; // 禁止奇偶校验功能
    pUsart->CR2.STOP = 0;       // 一个停止位
    
    // Step5: 设置波特率为115200，默认PCLK频率为8MB
    // 波特率 = PCLK频率 / (16 * DIV)
    pUsart->BRR.DIV_Mantissa = 4;   // 整数部分,115200=4,9600=52
    pUsart->BRR.DIV_Fraction = 5;    // 小数部分,115200=5,9600=1

    // Step6: 使能接收发送功能
    pUsart->CR1.RE = ENABLE;    // 使能串口接收功能
    pUsart->CR1.TE = ENABLE;    // 使能串口发送功能，该操作会发送一个空闲帧，并清除TXE位

    // Step7: 配置中断
    pUsart->CR1.IDLEIE = DISABLE;   // 禁止IDLE中断
    pUsart->CR1.RXNEIE = DISABLE;   // 禁止RXNEIE中断，接收缓冲区非空
    pUsart->CR1.TCIE   = DISABLE;   // 禁止TCIE发送完成中断
    pUsart->CR1.TXEIE  = DISABLE;   // 禁止TXEIE发送缓冲区为空中断
    pUsart->CR1.PEIE   = DISABLE;   // 禁止PEIE中断
    
    // Step8: 配置DMA

    // Step9: 关闭其它功能不需要的功能，比如LIN模式/红外等

    // Step9: 设置UE激活USART和发送接收功能
    pUsart->CR1.UE = ENABLE;        // 使能串口

    USART_SyncTransmit(USART_IDX1, "Usart init done.\r\n");

    return RET_OK;
}

// 跳转到main函数前的初始化
const SYS_INIT_PF g_apfSysInitBeforeMain[] = {
    SysHwClkInitBeforeMain,
    SysGpioInitBeforeMain,
    SysUsart1InitBeforeMain
};

// 系统跳转到Main之前的初始化总入口，汇编里调用
void SYS_InitBeforeMain(void)
{
    uint32_t ret;
    uint32_t i;
    SYS_INIT_PF pfFunc = NULL;

    for (i = 0; i < ARR_SIZE(g_apfSysInitBeforeMain); i++) {
        pfFunc = g_apfSysInitBeforeMain[i];
        ret = pfFunc();
        if (ret != RET_OK) {
            USART_SyncTransmit(USART_IDX1, "SYS_InitBeforeMain fail\r\n");
            ASSERT(""SYS_InitBeforeMain fail\r\n"");
        }
    }

    return;
}

/**
 * @brief  控制器初始化函数，提供给汇编代码使用，在执行main函数前，必须先初始化完成
 *         初始化包括Flash接口，PLL时钟，系统时钟等
 * @note   只有当系统复位后才能调用此函数
 * @param  无
 * @retval 无
 */
void MAIN_SystemInit(void)
{
    /* Reset the RCC clock configuration to the default reset state(for debug* purpose) */
    /* Set HSION bit */
    // 使能内部时钟HSI，默认复位后就是使能的
    // RCC->CR.HSION = RCC_CR_HSION_ENABLE;
    RCC->CR.reg |= 0x00000001U;

    /* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */
    // 设置默认时钟配置，采用HSI，APB1和APB2不分频
    RCC->CFGR.reg &= 0xF8FF0000U;
    // RCC->CFGR.MC0 = RCC_CFGR_MCO_NOCLOCK;        // 不输出时钟
    // RCC->CFGR.ADCPRE = RCC_CFGR_ADCPRE_DIV2;     // ADC时钟二分频
    // RCC->CFGR.PPRE2 = RCC_CFGR_PPRE2_DIV1;       // APB2时钟不分频
    // RCC->CFGR.PPRE1 = RCC_CFGR_PPRE1_DIV1;       // APB1时钟不分频
    // RCC->CFGR.HPRE = RCC_CFGR_HPRE_DIV1;         // AHB时钟不分频，即等于SYSCLK
    // RCC->CFGR.SW = RCC_CFGR_SW_HSI;              // HSI作为SYSCLK时钟，默认等于8MHz

    /* Reset HSEON, CSSON and PLLON bits */
    // 关闭PLL，CSS，HSE
    RCC->CR.reg &= 0xFEF6FFFFU;

    /* Reset HSEBYP bit */
    // 外部4-16MHz振荡器没有旁路
    RCC->CR.reg &= 0xFFFBFFFFU;

    /* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */
    // PLLSRC=0(HSI振荡器时钟经2分频后作为PLL输入时钟)  PLLXTPRE=0(HSE不分频)
    // PLLMUL=0(PLL 2倍频输出)  USBPRE=0(PLL时钟1.5倍分频作为USB时钟)
    RCC->CFGR.reg &= 0xFF80FFFFU;

    /* Disable all interrupts and clear pending bits */
    // 关闭所有时钟中断，并写1清零状态
    RCC->CIR.reg = 0x009F0000U;

    /* Vector Table Relocation in Internal FLASH. */
    // 配置向量表，默认保存在FLASH，偏移0的位置
    SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET;

    return;
}